﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using log4net;
using log4net.Config;
using log4net.Core;
using log4net.Repository.Hierarchy;

namespace HashFoo.MigratorFoo.Console
{
    internal class Program
    {
        static readonly ILog Log = LogManager.GetLogger("MigratorFoo");
        static MigrationManagerWorker _worker;

        static FileInfo FindFirstMigratorFooConfig()
        {
            var file = Directory.GetFiles(".\\", "*.migratorfoo").Concat(Directory.GetFiles(".\\", "migratorfoo.xml")).FirstOrDefault();

            return file == null ? null : new FileInfo(file);
        }

        static MigrationConfiguration LoadMigrationConfig(CommandArgs inputs)
        {
            MigrationConfiguration config;
            try
            {
                config = MigrationConfiguration.Load(inputs.ConfigPath, inputs.BuildConfig);
            }
            catch (Exception e)
            {
                Log.Fatal(e);
                System.Console.WriteLine(Resources.Program_Main_ErrorLoadingConfig, e);

                throw;
            }

            if (!File.Exists(config.DbConfigAssemblyPath))
            {
                Log.FatalFormat("DbConfigAssemblyPath does not exist: {0}", config.DbConfigAssemblyPath);
                throw new FileNotFoundException("Database config assembly doesn't exist: " + config.DbConfigAssemblyPath);
            }

            return config;
        }

        static int Main(string[] args)
        {
            AppDomain.CurrentDomain.UnhandledException +=
                (sender, eventArgs) =>
                {
                    System.Console.Error.WriteLine(Resources.Program_Main_Unhandled_exception___See_error_messages);
                    if (_worker != null) _worker.Dispose();
                };

            try
            {
                return RunMigratorFoo(args);
            }
            catch (Exception e)
            {
                // print out args
                System.Console.Error.WriteLine("Number of args: " + args.Length);
                foreach (var s in args) System.Console.Error.WriteLine("arg: " + s);

                System.Console.Error.WriteLine(Resources.Program_Main_Unhandled_exception___See_error_messages);
                System.Console.Error.WriteLine(e.ToString());
                if (_worker != null)
                {
                    _worker.Dispose();
                    _worker = null;
                }

                return -1;
            }
        }

        static int RunMigratorFoo(string[] args)
        {
            XmlConfigurator.Configure();

#if DEBUG
            Debugger.Break();
#endif
            //Parse the command line
            var inputs = CommandArgs.Parse(args);

            ConfigureLogging(inputs);

            //If a connection config is not present, find the first or default one
            if (string.IsNullOrWhiteSpace(inputs.ConfigPath))
            {
                var defaultConfig = FindFirstMigratorFooConfig();

                if (defaultConfig == null || !defaultConfig.Exists)
                {
                    Log.Debug("No config found?");
                    System.Console.WriteLine(CommandArgs.Usage);
                    return -1;
                }

                Log.DebugFormat("Found migratorfoo configuration file: {0}", defaultConfig.FullName);
                System.Console.WriteLine(Resources.Program_Main_UsingConfigFile_0_, defaultConfig.FullName);
                inputs.ConfigPath = defaultConfig.FullName;
            }

            var config = LoadMigrationConfig(inputs);
            PrintConfig(config);

            _worker = new MigrationManagerWorker(config);
            if (!string.IsNullOrWhiteSpace(inputs.ConfigName)) _worker.SetActiveDbConfig(inputs.ConfigName);

            return RunTarget(inputs, _worker);
        }

        static void ConfigureLogging(CommandArgs inputs)
        {
            // configure logging based on verbosity
            foreach (var logRepo in LogManager.GetAllRepositories())
            {
                var threshold = inputs.Verbose ? Level.Debug : Level.Info;
                logRepo.Threshold = threshold;
                foreach (var logger in logRepo.GetCurrentLoggers()) ((Logger)logger).Level = threshold;
            }
        }

        static void PrintConfig(MigrationConfiguration defaultConfig)
        {
            Log.DebugFormat(
                "Assembly Path: {0}, exists: {1}",
                defaultConfig.DbConfigAssemblyPath,
                File.Exists(defaultConfig.DbConfigAssemblyPath) ? "true" : "false");

            Log.DebugFormat("Down Scripts Path: {0}", defaultConfig.DownScriptsDirectory);
            Log.DebugFormat("Up Scripts Path: {0}", defaultConfig.UpScriptsDirectory);
            Log.DebugFormat("Schema Table Name: {0}", defaultConfig.SchemaVersionTableName);
        }

        static int RunTarget(CommandArgs inputs, MigrationManagerWorker manager)
        {
            switch (inputs.Target)
            {
                case CommandArgs.TargetCommands.Generate:
                    RunGen(manager);
                    break;
                case CommandArgs.TargetCommands.Regen:
                    RunReGen(manager, inputs);
                    break;
                case CommandArgs.TargetCommands.RemovePending:
                    manager.RemovePendingMigrations();
                    break;
                case CommandArgs.TargetCommands.Apply:
                    if (inputs.Version != 0) manager.Apply(inputs.Version);
                    else manager.Apply();
                    break;
                case CommandArgs.TargetCommands.ApplyAll:
                    manager.ApplyAll();
                    break;
                case CommandArgs.TargetCommands.Rollback:
                    if (inputs.Version > 0) manager.Rollback(inputs.Version);
                    else manager.Undo();
                    break;
                case CommandArgs.TargetCommands.Reset:
                    manager.Reset();
                    break;
                case CommandArgs.TargetCommands.ListDbConfigs:
                    System.Console.WriteLine();
                    System.Console.WriteLine(Resources.List_DBConfigNames_Header);
                    manager.DbConfigNames.ToList().ForEach(System.Console.WriteLine);
                    System.Console.WriteLine();
                    break;
                case CommandArgs.TargetCommands.Undo:
                    manager.Undo();
                    break;
                case CommandArgs.TargetCommands.Nuke:
                    RunNuke(inputs, manager);
                    break;
                default:
                    System.Console.WriteLine(Resources.Program_Main_NoMigrationTargetSelected + CommandArgs.Usage);
                    return -1;
            }

            return 0;
        }

        static void RunNuke(CommandArgs inputs, MigrationManagerWorker manager)
        {
            if (!inputs.Force)
            {
                System.Console.Write(Resources.ConfirmNuke);
                var userRepsonse = (System.Console.ReadLine() ?? "N").ToUpperInvariant();
                if (userRepsonse.StartsWith("Y")) manager.Nuke(inputs.MasterConnectionString);
            }
            else
            {
                manager.Nuke(inputs.MasterConnectionString);
            }
        }

        static void RunGen(MigrationManagerWorker manager)
        {
            if (manager.HasPendingMigrations)
            {
                System.Console.Error.WriteLine("You have pending migrations, remove them.");
                return;
            }

            manager.Generate();
        }

        static void RunReGen(MigrationManagerWorker manager, CommandArgs inputs)
        {
            if (manager.HasPendingMigrations && !inputs.Force)
            {
                System.Console.WriteLine(Resources.Program_RunReGen_You_have_pending_migrations__delete_them___Y_N_);
                if ((System.Console.ReadLine() ?? "").ToUpperInvariant().StartsWith("Y") == false) return;
            }

            manager.Generate(true);
        }
    }
}